// DESCRIPTION: Verilator: Verilog Test module
//
// Copyright 2023 by Wilson Snyder. This program is free software; you can
// redistribute it and/or modify it under the terms of either the GNU
// Lesser General Public License Version 3 or the Perl Artistic License
// Version 2.0.
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0

`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d:  got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); $stop; end while(0);
`define check_range(gotv,minv,maxv) do if ((gotv) < (minv) || (gotv) > (maxv)) begin $write("%%Error: %s:%0d:  got=%0d exp=%0d-%0d\n", `__FILE__,`__LINE__, (gotv), (minv), (maxv)); $stop; end while(0);
`define check_within_30_percent(gotv,val) `check_range((gotv), (val) * 70 / 100, (val) * 130 / 100)

module t;

  localparam int COUNT = 1000;

  int seq;
  int counts[8];

  function automatic int sfunc();
    int fv;
    fv = 2;
    randsequence(main)
      main : one;
      one : { fv = 1; };
    endsequence
    return fv;
  endfunction

  function void prep();
    for (int i = 0; i < COUNT; ++i) counts[i] = 0;
  endfunction

  initial begin
    int switch;
    int x;
    int wgt;

    x = 0;
    randsequence()
      main : { x = 10; };
      ignore : { x = 20; };
    endsequence
    `checkd(x, 10);

    x = 0;
    randsequence(first)
      ignore : { x = 20; };
      first : { x = 10; };
    endsequence
    `checkd(x, 10);

    if (sfunc() != 1) $stop;

    x = 0;
    randsequence(main)
      main : sub;
      sub : { x += 10; };
    endsequence
    `checkd(x, 10);

    x = 0;
    switch = 1;
    randsequence(main)
      main : case (switch)
        default : third;  // Not listed first; need to move internally to last position
        0 : zero;
        1 : first;
      endcase;
      zero : { x = 0; };
      first : { x += 10; };
      third : { x += 3; };
    endsequence
    `checkd(x, 10);

    x = 0;
    randsequence(main)
      main : first;  // Check single rules
      first : { x += 20; };
    endsequence
    `checkd(x, 20);

    x = 0;
    randsequence(main)
      main : zero := 0;  // Check single zero-weight
      zero : { x += 20; };
    endsequence
    `checkd(x, 0);

    x = 0;
    wgt = 1;
    for (int i=0; i<2; ++i) begin
      randsequence()
        main : first := wgt { wgt = 0; };
        first : { x += 1000; };
      endsequence
    end
    `checkd(wgt, 0);
    `checkd(x, 1000);

    x = 0;
    wgt = 1;
    for (int i=0; i<2; ++i) begin
      randsequence()
        main : first := wgt { wgt = 0; }
          | second := (1 - wgt) { };
        first : { x += 1000; };
        second : { x += 10; };
      endsequence
    end
    `checkd(wgt, 0);
    `checkd(x, 1010);

    x = 0;
    randsequence(main)
      main : first second;
      first : { x += 20; };
      second : { x += 2; };
    endsequence
    `checkd(x, 22);

    // simple
    prep();
    seq = 0;
    randsequence(main)
      main: one two three;
      two: { `checkd(seq, 1); seq = 2; };
      one: { `checkd(seq, 0); seq = 1; };
      three: { `checkd(seq, 2); seq = 3; };
    endsequence
    `checkd(seq, 3);

    // simple unnamed
    prep();
    seq = 0;
    randsequence()
      unnamed: { seq = 2; };
    endsequence
    `checkd(seq, 2);

    // weight
    prep();
    for (int i = 0; i < COUNT; ++i) begin
      randsequence(main)
        main: one | two | three := 2;
        one: { ++counts[0]; };
        two: { ++counts[1]; };
        three: { ++counts[2]; };
      endsequence
    end
    `check_within_30_percent(counts[0], COUNT * 1 / 4);
    `check_within_30_percent(counts[1], COUNT * 1 / 4);
    `check_within_30_percent(counts[2], COUNT * 2 / 4);

    // case
    prep();
    for (int i = 0; i < COUNT; ++i) begin
      randsequence(main)
        main: one_if;
        one_if: if (i % 10 == 0) count_1 else most;
        count_1: { ++counts[1]; };
        count_2: { ++counts[2]; };
        count_3: { ++counts[3]; };
        count_4: { ++counts[4]; };
        bad: { $stop; };
        most: case (i % 10)
              0: bad;
              1, 2: count_2;
              3, 4, 5: count_3;
              default: count_4;
            endcase;
      endsequence
    end
    `check_within_30_percent(counts[1], COUNT * 1 / 10);
    `check_within_30_percent(counts[2], COUNT * 2 / 10);
    `check_within_30_percent(counts[3], COUNT * 3 / 10);
    `check_within_30_percent(counts[4], COUNT * 4 / 10);

    // case - different default
    prep();
    for (int i = 0; i < COUNT; ++i) begin
      randsequence(main)
        main: one_if;
        one_if: if (i % 10 == 0) count_1 else most;
        count_1: { ++counts[1]; };
        count_2: { ++counts[2]; };
        count_3: { ++counts[3]; };
        count_4: { ++counts[4]; };
        bad: { $stop; };
        most: case (i % 10)
              0: bad;
              1, 2: count_2;
              3, 4, 5: count_3;
              default count_4;  // No :
            endcase;
      endsequence
    end
    `check_within_30_percent(counts[1], COUNT * 1 / 10);
    `check_within_30_percent(counts[2], COUNT * 2 / 10);
    `check_within_30_percent(counts[3], COUNT * 3 / 10);
    `check_within_30_percent(counts[4], COUNT * 4 / 10);

    // repeat
    prep();
    randsequence(main)
      main: repeat(10) count_1;
      count_1: { ++counts[1]; };
    endsequence
    `checkd(counts[1], 10);

    // return
    prep();
    for (int i = 0; i < COUNT; ++i) begin
      automatic bit fiftyfifty = i[0];
      randsequence(main)
        main: count_1 check count_2;
        check: count_3 { if (fiftyfifty) return; } count_4;
        count_1: { ++counts[1]; };
        count_2: { ++counts[2]; };
        count_3: { ++counts[3]; };
        count_4: { ++counts[4]; };
      endsequence
    end
    `checkd(counts[1], COUNT * 1 / 1);
    `checkd(counts[2], COUNT * 1 / 1);  // return
    `checkd(counts[3], COUNT * 1 / 1);
    `checkd(counts[4], COUNT * 1 / 2);  // break or return

    $write("*-* All Finished *-*\n");
    $finish;
  end

endmodule
